import cv2
import numpy as np


class Interpolation:
    def __init__(self, type):
        self.type = type
        
    def __call__(self, src, size):
        """
            Interpolate
            size: target size
            type: interpolation type
        """

        sh, sw = src.shape

        src = src.tolist()

        th, tw = size

        dst = np.zeros(size, 'float32').tolist()

        if self.type == 'NEAREST':
            def sampler(y, x):
                y = round(y * (sh - 1))
                x = round(x * (sw - 1))
                return src[y][x]
        elif self.type == 'BILINEAR':
            def sampler(y, x):
                y = (y * (sh - 1))
                x = (x * (sw - 1))
                y0 = int(y)
                x0 = int(x)
                y1 = min(int(y + 1), sh - 1)
                x1 = min(int(x + 1), sw - 1)
                if y0 == y1:
                    alpth = 1.0
                else:
                    alpth = (y - y0) / (y0 + y1)
                if x0 == x1:
                    beta = 1.0
                else:
                    beta = (x - x0) / (x0 + x1)
                return src[y0][x0] * (1 - alpth) * (1 - beta)\
                     + src[y0][x1] * alpth * (1 - beta)\
                     + src[y1][x0] * (1 - alpth) * beta\
                     + src[y1][x1] * alpth * beta
        elif self.type == 'BICUBIC':
            def w(x):
                """
                    weight function
                """
                a = -0.5
                x = abs(x)
                if x <= 1:
                    return (a + 2) * x ** 3 - (a + 3) * x ** 2 + 1
                elif x < 2:
                    return a * x ** 3 - 5 * a * x ** 2 + 8 * a * x - 4 * a
                else:
                    return 0
            def sampler(y, x):
                y = (y * (sh - 1))
                x = (x * (sw - 1))
                y1 = int(y)
                x1 = int(x)
                y0 = max(int(y - 1), 0)
                x0 = max(int(x - 1), 0)
                y2 = min(int(y + 1), sh - 1)
                x2 = min(int(x + 1), sw - 1)
                y3 = min(int(y + 2), sh - 1)
                x3 = min(int(x + 2), sw - 1)
                f = 0
                W = 0
                for yi in [y0, y1, y2, y3]:
                    for xi in [x0, x1, x2, x3]:
                        k = w(x - xi) * w(y - yi)
                        f += src[yi][xi] * k
                        W += k
                f /= W
                return f

        for y in range(0, th):
            for x in range(0, tw):
                dst[y][x] = sampler(y / (th - 1), x / (tw - 1))

        dst = np.array(dst).astype('uint8')

        return dst


if __name__ == '__main__':

    def down_sample(src):
        h, w = src.shape
        h = h // 2
        w = w // 2
        ys = [2 * y for y in range(h)]
        xs = [2 * x for x in range(w)]
        dst = src[ys, :][:, xs]
        return dst

    ORI = cv2.imread('barb.bmp', cv2.IMREAD_GRAYSCALE)
    SRC = down_sample(ORI)
    SIZE = (512, 512)

    def show(winname, img):
        cv2.imshow(winname, img)
        cv2.resizeWindow(winname, 512, 512)
        cv2.waitKey(1)

    def loss(y, label):
        return 10 * np.log10(255 ** 2 / np.mean((label - y) ** 2))

    y1 = Interpolation('NEAREST')(SRC, SIZE)
    show('NEAREST', y1)
    print('NEAREST', loss(y1, ORI))

    y2 = Interpolation('BILINEAR')(SRC, SIZE)
    show('BILINEAR', y2)
    print('BILINEAR', loss(y2, ORI))

    y3 = Interpolation('BICUBIC')(SRC, SIZE)
    show('BICUBIC', y3)
    print('BICUBIC', loss(y3, ORI))

    cv2.waitKey(0)